package com.mxx.common.netty.http;

import com.mxx.common.netty.exception.IllegalParameterException;
import com.mxx.common.netty.exception.IllegalReturnTypeException;
import com.mxx.common.conv.JacksonConverter;
import com.mxx.common.model.Response;
import com.mxx.common.utils.Strings;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.EmptyByteBuf;
import io.netty.handler.codec.http.HttpResponseStatus;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;


/**
 * @Auther: Mr. Zeng
 * @Date: 2020/8/3 17:24
 * @Description: 暂无描述
 */
@Slf4j
public abstract class AbstractNettyHttpResponseFactory implements NettyHttpResponseFactory {
    protected JacksonConverter jacksonConverter;
    protected NettyHttpResponseTemplate template;
    private String projectPackageName;

    public AbstractNettyHttpResponseFactory(JacksonConverter jacksonConverter, NettyHttpResponseTemplate template) {
        this.jacksonConverter = jacksonConverter;
        this.template = template;
        String threadStartEntry = System.getProperty("sun.java.command");
        projectPackageName = threadStartEntry.substring(0, threadStartEntry.lastIndexOf("."));


    }


    @Override
    public NettyHttpResponse catchResponse(Object result) {
        if (result == null) {
            return this.withJson("");
        } else if (result instanceof HttpResponseStatus) {
            return this.newResponse((HttpResponseStatus) result);
        } else if (result instanceof Throwable) {
            return this.newResponse((Throwable) result);
        } else if (result instanceof NettyHttpResponse) {
            return ((NettyHttpResponse) result);
        } else if (result instanceof Serializable) {
            return this.withJson((Serializable) result);
        } else {
            throw new IllegalReturnTypeException("illegal return  type , returnType is [%s]", result.getClass());
        }
    }

    @Override
    public NettyHttpResponse catchThrowable(Throwable ex) {
        if (ex instanceof IllegalParameterException) {
            log.error("handle verifier parameter failure ,message [{}]", ex.getMessage());
            return this.newResponse(HttpResponseStatus.BAD_REQUEST,new Response<>(HttpResponseStatus.BAD_REQUEST.code(), ex.getMessage()));
        } else if (ex instanceof InvocationTargetException) {
            Throwable throwable = ((InvocationTargetException) ex).getTargetException();
            String exceptionMessage = this.getExceptionMessage(throwable, "handle execute exception");
            log.error(exceptionMessage, ex);
            return this.newResponse(throwable);
        } else {
            String exceptionMessage = this.getExceptionMessage(ex, "handle execute error");
            log.error(exceptionMessage, ex);
            return this.newResponse(ex);
        }
    }

    public NettyHttpResponse newResponse(HttpResponseStatus status, Serializable content) {
        return new NettyHttpResponse(status, toJSON(content));
    }

    public NettyHttpResponse newResponse(HttpResponseStatus status, String content) {
        return new NettyHttpResponse(status, content);
    }

    public NettyHttpResponse newResponse(HttpResponseStatus status) {
        return this.newResponse(status, template.with(status));
    }

    @Override
    public NettyHttpResponse newEmpty() {
        return new NettyHttpResponse(HttpResponseStatus.OK, new EmptyByteBuf(ByteBufAllocator.DEFAULT));
    }

    public NettyHttpResponse newResponse(Throwable exception) {
        return this.newResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, template.with(exception));
    }

    @Override
    public NettyHttpResponse newRedirect(String url) {
        return this.newResponse(HttpResponseStatus.PERMANENT_REDIRECT).withRedirect(url);
    }

    public NettyHttpResponse newResponse(HttpResponseStatus status, ByteBuf byteBuf) {
        return new NettyHttpResponse(status, byteBuf);
    }


    public NettyHttpResponse ok(Serializable content) {
        return this.newResponse(HttpResponseStatus.OK, content);
    }

    public NettyHttpResponse ok(String content) {
        return this.newResponse(HttpResponseStatus.OK, content);
    }


    public NettyHttpResponse ok() {
        return this.newResponse(HttpResponseStatus.OK);
    }


    protected String toJSON(Serializable data) {
        try {
            if (Strings.isEmpty(data)) {
                return "{}";
            }
            if (data instanceof String) {
                return (String) data;
            }
            return jacksonConverter.writeValueAsString(data);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    protected Throwable getDataRealException(Throwable throwable) {
        String className = throwable.getClass().getName();
        if ((className.startsWith("org.springframework.dao") || className.startsWith("org.springframework.orm")) && throwable.getCause() != null) {
            throwable = throwable.getCause();
            className = throwable.getClass().getName();
            if (className.startsWith("org.hibernate.exception") && throwable.getCause() != null) {
                throwable = throwable.getCause();
                if (throwable.getCause() != null) {
                    throwable = throwable.getCause();
                }
            }
        }
        return throwable;
    }


    protected String getExceptionMessage(Throwable throwable, String msg) {
        Throwable messageThrowable = getDataRealException(throwable);
        StackTraceElement[] stackTraceElements = throwable.getStackTrace();

        StringBuilder message = new StringBuilder(msg);
        message.append(" [").append(messageThrowable.getClass().getName()).append(": ").append(messageThrowable.getMessage()).append("]");
        for (int i = 0; i < stackTraceElements.length; i++) {
            StackTraceElement stackTraceElement = stackTraceElements[i];

            if (stackTraceElement.getClassName().startsWith(projectPackageName) && stackTraceElement.getLineNumber() != -1) {
                message.append("\n\tat ").append(stackTraceElement.getClassName())
                        .append(".")
                        .append(stackTraceElement.getMethodName())
                        .append("(")
                        .append(stackTraceElement.getFileName()).append(":")
                        .append(stackTraceElement.getLineNumber()).append(")");
            }
        }
        return message.toString();
    }


}
